#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <assert.h>

#include <time.h>
#include <stdarg.h>
#include <sys/types.h>
#include <unistd.h>

#include <log.h>
#include <appname.h>
#include <utility.h>
#include <strhelper.h>

static Log  *_def_log         = NULL;
static int   _fmt_buffer_size = 0;
static char *_fmt_buffer      = NULL;

Log *log_get()
{
    if (_def_log == NULL) {
        assert(appname);
        _def_log = new Log(appname);
    }

    return _def_log;
}

Log::Log(const char *bn) : base_name(bn)
                         , multi_thread_p(true)
                         , multi_file_p(true)
                         , no_time_suffix_p(false)
                         , stop_log(false)
                         , max_size(1024 * 1024 * 512)
                         , fp(NULL)
                         , repeat_cnt(0)
                         , remove_repeat(true)
                         , with_pid_p(false)
{
    if (_def_log == NULL)
        _def_log = this;
}

Log::~Log()
{
    if (fp) {
        std::string ts, pid;

        if (repeat_cnt > 0) {
            std::string ts;
            get_curr_time_fmt(ts);
            fprintf(fp, "%s (repeat %d times)\n",
                    ts.c_str(), repeat_cnt);
        }

        get_curr_time_fmt(ts);
        str_append(pid, getpid());
        fprintf(fp, "%s === logger end %s ===\n",
                ts.c_str(), pid.c_str());

        fclose(fp);
    }

    if (this == _def_log)
        _def_log = NULL;
}

#define _GET_FORMAT_STRING(N)                                           \
    do {                                                                \
        int      n;                                                     \
        va_list  ap;                                                    \
        char    *np;                                                    \
                                                                        \
        if (_fmt_buffer == NULL) {                                      \
            _fmt_buffer_size = 4096;                                    \
            _fmt_buffer = (char *) malloc (_fmt_buffer_size);           \
                                                                        \
            if (_fmt_buffer == NULL) {                                  \
                N = -1;                                                 \
                break;                                                  \
            }                                                           \
        }                                                               \
                                                                        \
        for (;;) {                                                      \
            va_start(ap, fmt);                                          \
            n = vsnprintf(_fmt_buffer, _fmt_buffer_size, fmt, ap);      \
            va_end(ap);                                                 \
                                                                        \
            if (n > -1 && n < _fmt_buffer_size) {                       \
                N = n;                                                  \
                break;                                                  \
            }                                                           \
                                                                        \
            _fmt_buffer_size *= 2;                                      \
            if ((np = (char *) realloc (_fmt_buffer,                    \
                                        _fmt_buffer_size)) == NULL) {   \
                free(_fmt_buffer);                                      \
                _fmt_buffer = NULL;                                     \
                N = -1;                                                 \
                break;                                                  \
            }                                                           \
            else {                                                      \
                _fmt_buffer = np;                                       \
            }                                                           \
        }                                                               \
    } while (0)

int Log::print(const char *fmt, ...)
{
    int r;

    if (multi_thread_p)
        lock_t::lock(write_mutex);

    _GET_FORMAT_STRING(r);

    if (r > 0)
        if (put_string(_fmt_buffer) < 0)
            r = -1;

    if (multi_thread_p)
        lock_t::unlock(write_mutex);

    return r;
}

int Log::print(const std::string& str)
{
    int  r;

    if (multi_thread_p)
        lock_t::lock(write_mutex);

    if (put_string(str.c_str()) < 0)
        r = -1;
    else
        r = 0;

    if (multi_thread_p)
        lock_t::unlock(write_mutex);

    return r;
}

int Log::put_string(const char *str)
{
    if (!beg_write())
        return -1;

    const char *p;
    std::string ts, line;

    char buf[64];
    if (with_pid_p)
        sprintf(buf, "(%d) ", getpid());

    while (*str) {
        p = strchr(str, '\n');

        if (p) {
            if (with_pid_p) {
                line = buf;
                line.append(str, (int) (p - str));
            }
            else
                line.assign(str, p - str);
            str = p + 1;
        }
        else {
            if (with_pid_p) {
                line = buf;
                line.append(str);
            }
            else
                line = str;
            str = str + strlen(str);
        }

        bool print_p = true;

        if (remove_repeat) {
            // repeat line
            if (last_line.size() > 0 && last_line == line) {
                ++repeat_cnt;
                print_p = false;
            }
            else {
                if (repeat_cnt > 0) {
                    get_curr_time_fmt(ts);
                    fprintf(fp, "%s (repeat %d times)\n",
                            ts.c_str(), repeat_cnt);
                    repeat_cnt = 0;
                }
                last_line  = line;
            }
        }

        if (print_p) {
            get_curr_time_fmt(ts);

            if (no_time_suffix_p)
                fseek(fp, SEEK_END, 0);

            fprintf(fp, "%s %s\n", ts.c_str(), line.c_str());
        }
    }

    fflush(fp);

    return end_write() ? 0 : -1;
}

bool Log::beg_write()
{
    if (stop_log)
        return false;

    if (fp == NULL) {
        if (dir.size() > 0)
            if (create_dir(dir.c_str()) == false)
                return false;

        if (no_time_suffix_p) {
            if (dir.size() > 0)
                fn = dir + "/" + base_name + ".log";
            else
                fn = base_name + ".log";

            fp = fopen(fn.c_str(), "a");
        }
        else {
            for (;;) {
                std::string ts;

                time_seq.get(ts);

                if (dir.size() > 0)
                    fn = dir + "/" + base_name + "-" + ts + ".log";
                else
                    fn = base_name + "-" + ts + ".log";

                if (!is_reg_file(fn.c_str()))
                    break;
            }

            fp = fopen(fn.c_str(), "w");
        }

        if (fp == NULL)
            return false;

        // write the log header
        std::string ts;
        std::string pid;
        get_curr_time_fmt(ts);
        str_append(pid, getpid());

        if (!no_time_suffix_p)
            fprintf(fp, "%s === logger begin %s ===\n",
                    ts.c_str(), pid.c_str());
    }

    return true;
}

bool Log::end_write()
{
    if (fp) {
        int pos = ftell(fp);

        if (pos >= max_size) {
            if (!no_time_suffix_p) {
                std::string ts, pid;
                get_curr_time_fmt(ts);
                str_append(pid, getpid());
                fprintf(fp, "%s === logger end %s ===\n",
                        ts.c_str(), pid.c_str());
            }

            fclose(fp);
            fp = NULL;

            if (no_time_suffix_p) {
                std::string ts;
                time_seq.get(ts);

                std::string bak_fn;

                for (;;) {
                    std::string ts;

                    time_seq.get(ts);

                    if (dir.size() > 0)
                        bak_fn = dir + "/" + base_name + "-" + ts + ".log";
                    else
                        bak_fn = base_name + "-" + ts + ".log";

                    if (!is_reg_file(bak_fn.c_str()))
                        break;
                }

                if (rename(fn.c_str(), bak_fn.c_str()) < 0)
                    stop_log = true;
            }

            if (!multi_file_p)
                stop_log = true;
        }
    }
    return true;
}

int log_print(const char *fmt, ...)
{
    Log *log = log_get();
    int  r;

    if (log->multi_thread_p)
        lock_t::lock(log->write_mutex);

    _GET_FORMAT_STRING(r);

    if (r > 0)
        if (log->put_string(_fmt_buffer) < 0)
            r = -1;

    if (log->multi_thread_p)
        lock_t::unlock(log->write_mutex);

    return r;
}

int log_print(const std::string& str)
{
    Log *log = log_get();
    int  r;

    if (log->multi_thread_p)
        lock_t::lock(log->write_mutex);

    if (log->put_string(str.c_str()) < 0)
        r = -1;
    else
        r = 0;

    if (log->multi_thread_p)
        lock_t::unlock(log->write_mutex);

    return r;
}

int log_close()
{
    if (_def_log) {
        delete _def_log;
        _def_log = NULL;
    }

    if (_fmt_buffer) {
        free (_fmt_buffer);
        _fmt_buffer = NULL;

        _fmt_buffer_size = 0;
    }

    return 0;
}

int log_get_fn(std::string& s)
{
    Log* log = log_get();

    s = log->fn;

    return (int) s.size();
}
